<?php

/*
 * Абстракный класс приложения.
 * Наследуется основными классами приложений.
 * 
 * Класс с "двойным дном" - статические методы относятся ко всем приложениям в целом,
 * динамические - к конкретному объекту-приложению
 * 
 */

namespace T4;

use T4\Exception;

abstract class Application {
    
    /**
     * ----------------------------------------
     * "Синглтонная часть"
     * ---------------------------------------- 
     */
    
    /**
     * Коллекция инстанцированных объектов приложений 
     * @var array 
     */
    protected static $instances = array();
    
    /**
     * Получение объекта приложения по его имени
     * @param string $name Имя приложения
     * @return \T4\Application Объект приложения 
     */
    final public static function getInstance($name) {
        if ( !isset(self::$instances[$name]) ) {
            $appClassName = '\\APPS\\' . ucfirst($name);
            if ( !class_exists($appClassName, true) )
                throw new Exception('Основной класс приложения ' . $name . ' не существует');
            self::$instances[$name] = new $appClassName;
        }
        return self::$instances[$name];
    }
    
    final protected function __clone() { }
    
    /**
     * ----------------------------------------
     * Статические свойства и методы
     * ----------------------------------------
     */

    /**
     * Название GET параметра для определения приложения
     */
    const GET_PARAM_NAME = '__application';
    
    /**
     * Папка для приложений по умолчанию 
     */
    const DIR = 'applications';

    /**
     * Приложение по умолчанию
     */
    const DEFAULT_APP = 'system';

    /**
     * Текущее приложение
     * @var string 
     */
    protected static $current;
    
    /**
     * Список доступных в системе приложений
     * @var array 
     */
    protected static $avaliable;

    /**
     * Проверка на существование приложения
     * 
     * @param string $name имя приложения
     * @return bool
     */
    public static function exists($name) {
        try {
            self::getInstance($name);
        } catch (Exception $e) {
			echo $e->getMessage();
            return false;
        }
        return true;
    }
    
    /**
     * Определяет имя приложения исходя из переданного в $_GET
     * @return string
     */
    public static function detectName() {
        
        if (is_null(self::$current)) {
			
            self::$current = isset($_GET[self::GET_PARAM_NAME]) ? $_GET[self::GET_PARAM_NAME] : '';

            if (empty(self::$current))
                self::$current = self::DEFAULT_APP;

			if (!self::exists(self::$current)) {
                throw new Exception('Определено неверное имя приложения: "' . self::$current . '"');
            }
			
        }

        return self::$current;
        
    }
    
    /**
     * Возвращает массив доступных приложений
     * Ключи массива - папки в ФС
     * @return array
     */
    public static function getAll() {
        
        if (is_null(self::$avaliable)) {

            self::$avaliable = array();

            $dir = dir( \ROOT_PATH . '/applications' );

            while (false !== ($entry = $dir->read())) {

                if ($entry{0} == '.') {
                    continue;
                }

                self::$avaliable[$entry]['dirname'] = $entry;
                self::$avaliable[$entry]['namespace'] = '\\APPS\\' . strtoupper($entry);
                
            }
            
        }

        return self::$avaliable;
    }
    
    /**
     * ----------------------------------------
     * Свойства и конструктор
     * ----------------------------------------
     */
    
    /**
     *
     * @var string Имя приложения в системе, например system 
     */
    public $name;

    /**
     *
     * @var string Пространство имен приложения, например \APPS\SYSTEM
     */
    public $namespace;

    /**
     *
     * @var string Полный абсолютный путь до папки с приложением 
     */
    public $path;

    /**
     *
     * @var T4\Config Объект-конфиг приложения 
     */
    public $config;

    /**
     *
     * @var T4\RoutingConfig Объект, содержащий правила роутинга для приложения
     */
    public $routing;

    /**
     *
     * @var T4\DB Экземпляр подключения к базе данных 
     */
    public $db;
    
    /**
     * Манифест приложения
     * Требует переопределения в классе приложения
     * @var array 
     */
    static protected $manifest = array();

    /**
     * Конструктор класса
     * Инициализирует приложение
     */
    final public function __construct() {

        $className = get_called_class();
        $ClassNameParts = explode('\\', $className);

        $this->name = strtolower($ClassNameParts[1]);
        $this->namespace = '\\APPS\\' . strtoupper($this->name);
        $this->path = \ROOT_PATH . '/' . self::DIR . '/' . $this->name;

        $this->config = Config::getInstance($this->path . '/config/config.yaml');
        $this->routing = Config::getInstance($this->path . '/config/routing.yaml');

		$this->db = DB::getInstance();
        
    }
    
    /**
     * ----------------------------------------
     * Динамические методы
     * ----------------------------------------
     */
    
    final public function getName() {
        return $this->name;
    }
    
    final public function __toString() {
        return $this->getName();
    }

    final public function getNameSpace() {
        return $this->namespace;
    }

    final public function getManifest() {
        return static::$manifest;
    }

    final public function getTitle() {
        $manifest = $this->getManifest();
        return isset($manifest['title']) ? $manifest['title'] : $this->name;
    }

    final public function getModelsList() {
        $manifest = $this->getManifest();
        return isset($manifest['models']) ? $manifest['models'] : array();
    }

    final public function getAdminPages() {
        $manifest = $this->getManifest();
        return isset($manifest['admin']['pages']) ? $manifest['admin']['pages'] : array();
    }

    final public function getView() {
        $view = new View(array(
                    $this->path . '/templates',
                    FrontController::$theme->path
                ));
        return $view;
    }

    /**
     * Методы, выполняющиеся до и после установки приложения
     * Могут быть переопределены в конкретных классах приложений
     */
    public function preInstall() { }

    public function postInstall() { }

    /**
     * Установка приложения
     */
    final public function install() {

        $this->preInstall();

        /**
         * Развертывание моделей приложения
         */
        $models = $this->getModelsList();
        foreach ($models as $model) {
            $modelClass = $this->namespace . '\\MODELS\\' . ucfirst($model);
            echo $modelClass . ' is installing...<br />';
            $modelClass::install();
            echo $modelClass . ' is installed...<br />';
        }

        $this->postInstall();
    }

    /**
     * Методы, выполняющиеся до и после удаления приложения
     * Могут быть переопределены в конкретных классах приложений
     */
    public function preUninstall() { }

    public function postUninstall() { }

    /**
     * Установка приложения
     */
    final public function uninstall() {

        $this->preUninstall();

        /**
         * Удаление моделей приложения
         */
        $models = $this->getModelsList();
        foreach ($models as $model) {
            $modelClass = $this->namespace . '\\MODELS\\' . ucfirst($model);
            echo $modelClass . ' is uninstalling...<br />';
            $modelClass::uninstall();
            echo $modelClass . ' is uninstalled...<br />';
        }

        $this->postUninstall();
        
    }

}